#pragma once

#include <algorithm>
#include <jsoncpp/json/json.h>
#include "index.hpp"

#include "log.hpp"

namespace ns_searcher
{
    class Searcher
    {
    private:
        ns_index::Index &index;

    public:
        Searcher()
            : index(ns_index::Index::getInstance())
        {
            // std::cout << "get instance: success" << std::endl;
            LOG(INFO, "Get Index instance success!");
        }
        ~Searcher() {}

    public:
        void InitSearcher(const std::string &input)
        {
            index.BuildIndex(input);
            // std::cout << "build index: success" << std::endl;
            LOG(INFO, "build index: success!");
        }

        /**
         * @query: target user searching
         * @json_string: out argument, a pointer to doc json string
         */
        void Search(const std::string &query, std::string *json_string)
        {
            // optim and debug code
            struct InvertedElemPrint{
                int docid;
                int weight;
                std::vector<std::string> words;
                InvertedElemPrint():docid(0), weight(0){}
            };

            // step1: split query to words
            std::vector<std::string> query_cut;
            ns_util::JiebaUtil::CutString(query, &query_cut);
            // step2: search all words in query_cut and find in 'index'
            // ns_index::InvertedList all_invertedList;
            std::vector<InvertedElemPrint> all_invertedList;
            std::unordered_map<uint64_t, InvertedElemPrint> tokens_map; // use key to fulfill distinct
            for (auto word : query_cut)
            {
                // unify lowercase
                boost::to_lower(word);

                ns_index::InvertedList *inverted_list = index.GetInvertedList(word);
                if (nullptr == inverted_list)
                {
                    continue;
                }
                for(const auto&elem:*inverted_list){
                    auto &item = tokens_map[elem.docId];
                    item.docid = elem.docId;
                    item.weight += elem.weight;
                    item.words.push_back(elem.word);
                }
                // all_invertedList.insert(all_invertedList.end(), inverted_list->begin(), inverted_list->end());
            }
            for(const auto &item:tokens_map){
                all_invertedList.push_back(std::move(item.second));
            }
            // step3: sort all invertedlist (by weight descending)
            std::sort(all_invertedList.begin(), all_invertedList.end(), [](InvertedElemPrint& e1, InvertedElemPrint e2)
                      { return e1.weight > e2.weight; });
            // step4: assemble a json string
            Json::Value root;
            for (auto &item : all_invertedList)
            {
                ns_index::DocInfo *doc = index.GetForwardIndex(item.docid);

                Json::Value elem;
                elem["title"] = doc->title;
                elem["desc"] = GetDesc(doc->content, item.words[0]); // desc != content
                elem["url"] = doc->url;
                root.append(std::move(elem));
            }
            // Json::StyledWriter writer;
            Json::FastWriter writer;
            *json_string = writer.write(root);
        }

    private:
        std::string GetDesc(const std::string &content, const std::string &keyWord)
        { // extract desc from content via keyWord
            // find keyWord
            auto iter = std::search(content.begin(), content.end(),
                                    keyWord.begin(), keyWord.end(),
                                    [](char x, char y)
                                    {
                                        return tolower(x) == tolower(y);
                                    });
            // extract str around keyWord
            enum
            {
                prev_stop = 50,
                next_stop = 100
            };
            std::size_t key_pos = std::distance(content.begin(), iter);
            std::size_t start = 0;
            std::size_t end = content.size() - 1;
            if (key_pos >= prev_stop + start)
            {
                start = key_pos - prev_stop;
            }
            if (end >= next_stop + key_pos)
            {
                end = key_pos + next_stop;
            }
            std::string ret = content.substr(start, end - start) + "...";
            // return desc
            return ret;
        }
    };
};
